﻿using System.Text;

namespace AMS.Services.Impl;

internal class ApproverConfigService : IApproverConfigService
{
    private readonly DbContext _context;

    public ApproverConfigService(DbContext context)
    {
        _context = context;
    }

    public async Task<ListResult<ApproverConfig.QueryModel>> GetListAsync(CancellationToken cancellationToken)
    {
        var result = new ListResult<ApproverConfig.QueryModel>();

        try
        {
            var approverConfigs = await _context.Set<ApproverConfig>()
                .Include(x => x.ApproverDetails).ThenInclude(x => x.Approver)
                .AsNoTracking()
                .ToListAsync(cancellationToken);

            result.Data = approverConfigs.Select(x => GetQueryModel(x));

            result.Status = OperationalResult.SUCCESS;
        }
        catch (Exception error)
        {
            result.Status = OperationalResult.ERROR;

            result.Message = error.ToString();
        }

        return result;
    }

    public async Task<OperationalResult> UpdateAsync(IEnumerable<ApproverConfig.UpdateModel> approverConfigs, CancellationToken cancellationToken)
    {
        var result = new OperationalResult();

        await using var transaction = await _context.Database.BeginTransactionAsync(cancellationToken);

        try
        {
            var entities = await _context.Set<ApproverConfig>()
                .Include(x => x.ApproverDetails).ThenInclude(x => x.Approver)
                .ToListAsync(cancellationToken);

            if (entities is null || entities.Count == 0)
            {
                result.Status = OperationalResult.FAILURE;

                result.Message = "0 rows affected.";

                return result;
            }

            var waitingForConvert = new List<ApproverConfig.UpdateModel>();

            var idx = 0;
            foreach (var config in approverConfigs)
            {
                var origin = entities[idx];

                var seqIdx = 1;
                if (config.ApproverDetails.Count > origin.ApproverDetails.Count)
                {
                    foreach (var item in config.ApproverDetails)
                    {
                        var target = origin.ApproverDetails.FirstOrDefault(x => x.Approver.Id == item);
                        if (target is null || target.Sort != seqIdx)
                        {
                            waitingForConvert.Add(config);

                            break;
                        }

                        seqIdx++;
                    }
                }
                else
                {
                    foreach (var item in origin.ApproverDetails)
                    {
                        var target = config.ApproverDetails.FirstOrDefault(x => x == item.Approver.Id);
                        if (target is 0 || item.Sort != seqIdx)
                        {
                            waitingForConvert.Add(config);

                            break;
                        }

                        seqIdx++;
                    }
                }

                idx++;
            }

            var wait = await ConvertToEntitiesAsync(waitingForConvert);

            var sql = DetectChange(wait);

            var affectedRows = 0;
            if (!string.IsNullOrEmpty(sql))
            {
                affectedRows = await _context.Database.ExecuteSqlRawAsync(sql, cancellationToken);
                await transaction.CommitAsync(cancellationToken);


                result.Message = $"{affectedRows} rows affected.";
            }
            else
            {
                result.Message = "未作任何更改。";
            }

            result.Status = affectedRows > 0 ? OperationalResult.SUCCESS : OperationalResult.FAILURE;
        }
        catch (Exception error)
        {
            await transaction.RollbackAsync(cancellationToken);

            result.Status = OperationalResult.ERROR;

            result.Message = error.ToString();
        }

        return result;

        async Task<(IEnumerable<ApproverConfig> waitForUpdateEntities, IEnumerable<ApproverConfigDetail> waitForRemoveEntities)> ConvertToEntitiesAsync(IEnumerable<ApproverConfig.UpdateModel> models)
        {
            var waitForUpdateEntities = new List<ApproverConfig>();
            var waitForRemoveEntities = new List<ApproverConfigDetail>();

            foreach (var model in models)
            {
                var entity = await _context.Set<ApproverConfig>().FirstAsync(x => x.Id == model.Id, cancellationToken);
                var clone = new ApproverConfig
                {
                    Id = entity.Id,
                    ApproverDetails = entity.ApproverDetails.Select(x => new ApproverConfigDetail
                    {
                        Id = x.Id,
                        Approver = x.Approver,
                        Sort = x.Sort,
                        ApproverConfigId = model.Id
                    }).ToArray()
                };

                var newDetails = model.ApproverDetails.Select((x, i) =>
                {
                    var approver = _context.Set<User>().Find(new object[] { x });

                    return new ApproverConfigDetail
                    {
                        Approver = approver!,
                        Sort = i + 1,
                        ApproverConfigId = model.Id
                    };
                }).ToArray();

                entity.ApproverDetails.Clear();

                foreach (var item in newDetails)
                {
                    var originDetail = clone.ApproverDetails.FirstOrDefault(x => x.Approver.Id == item.Approver.Id);

                    if (originDetail is not null)
                    {
                        item.Id = originDetail.Id;
                    }

                    entity.ApproverDetails.Add(item);
                }

                foreach (var item in clone.ApproverDetails)
                {
                    var map = entity.ApproverDetails.FirstOrDefault(x => x.Approver.Id == item.Approver.Id);

                    if (map is null)
                    {
                        waitForRemoveEntities.Add(item);
                    }
                }

                await _context.Set<ApproverConfigDetail>().AddRangeAsync(entity.ApproverDetails.Where(x => x.Id == 0), cancellationToken);

                waitForUpdateEntities.Add(entity);
            }

            return (waitForUpdateEntities, waitForRemoveEntities);
        }

        string DetectChange((IEnumerable<ApproverConfig> waitForUpdateEntities, IEnumerable<ApproverConfigDetail> waitForRemoveEntities) wait)
        {
            var sb = new StringBuilder();
            foreach (var item in wait.waitForUpdateEntities)
            {
                foreach (var detail in item.ApproverDetails)
                {
                    if (detail.Id != 0)
                    {
                        sb.AppendLine($"Update \"ApproverConfigDetail\" Set \"Sort\" = {detail.Sort}, \"ApproverId\" = {detail.Approver.Id} Where \"Id\" = {detail.Id};");
                    }
                    else
                    {
                        sb.AppendLine($"Insert Into \"ApproverConfigDetail\"(\"ApproverId\", \"Sort\", \"ApproverConfigId\", \"CreatedDate\", \"LastModifiedDate\", \"IsDeleted\") Values({detail.Approver.Id}, {detail.Sort}, {detail.ApproverConfigId}, '{detail.CreatedDate.ToString("yyyy-MM-dd HH:mm:ss")}', '{detail.LastModifiedDate.ToString("yyyy-MM-dd HH:mm:ss")}', {detail.IsDeleted});");
                    }
                }
            }

            foreach (var item in wait.waitForRemoveEntities)
            {
                sb.AppendLine($"Delete From \"ApproverConfigDetail\" Where \"Id\" = {item.Id};");
            }

            return sb.ToString();
        }
    }
    public async Task<ListResult<object>> GetApproverListAsync(CancellationToken cancellationToken)
    {
        var result = new ListResult<object>();

        try
        {
            var approvers = await _context.Set<User>()
                .Include(x => x.Roles)
                .Include(x => x.Department).ThenInclude(x => x.Roles)
                .Select(x => new { x.Id, x.Name, Roles = x.Roles.Select(x => new { x.Name, x.Types }), DepartmentRoles = x.Department.Roles.Select(x => new { x.Name, x.Types }) })
                .AsNoTracking()
                .ToListAsync(cancellationToken);

            var filterApprovers = approvers
                .Where(x => x.Name != "admin" && (x.Roles.Any(x =>
                    (x.Types.Contains("Approval:PassAsync(Int32)") && x.Types.Contains("Approval:RejectAsync(Int32,String)") &&
                    !x.Types.Contains("~Approval:PassAsync(Int32)") && !x.Types.Contains("~Approval:RejectAsync(Int32,String)")) ||
                    (x.Types.Contains("#") && !x.Types.Contains("~Approval:PassAsync(Int32)") && !x.Types.Contains("~Approval:RejectAsync(Int32,String)"))
                ) || x.DepartmentRoles.Any(x =>
                    (x.Types.Contains("Approval:PassAsync(Int32)") && x.Types.Contains("Approval:RejectAsync(Int32,String)") &&
                    !x.Types.Contains("~Approval:PassAsync(Int32)") && !x.Types.Contains("~Approval:RejectAsync(Int32,String)")) ||
                    (x.Types.Contains("#") && !x.Types.Contains("~Approval:PassAsync(Int32)") && !x.Types.Contains("~Approval:RejectAsync(Int32,String)")))))
                .Select(x => new { x.Id, x.Name })
                .ToList();

            result.Data = filterApprovers;

            result.Status = OperationalResult.SUCCESS;
        }
        catch (Exception error)
        {
            result.Status = OperationalResult.ERROR;

            result.Message = error.ToString();
        }

        return result;
    }

    public static ApproverConfig.QueryModel GetQueryModel(ApproverConfig approverConfig)
    {
        return new ApproverConfig.QueryModel
        {
            Id = approverConfig.Id,
            CreatedDate = approverConfig.CreatedDate,
            LastModifiedDate = approverConfig.LastModifiedDate,
            Key = approverConfig.Key,
            Name = approverConfig.Name,
            ApproverDetails = approverConfig.ApproverDetails
                .Select(x => new { x.Approver, x.Sort })
                .OrderBy(x => x.Sort)
                .Select(x => x.Approver.Id)
        };
    }
}